﻿
using EmperialApps.WeatherSpark.Data;
using System;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;

namespace EmperialApps.WeatherSpark.Agent {

    /// <summary>Renders the graphs for the back of the Windows Phone flip live tile.</summary>
    public partial class TileFlipImage : TileImageBase {

        /// <summary>Initializes a new instance of the <see cref="TileFlipImage"/> class.</summary>
        public TileFlipImage( ) {
            InitializeComponent( );
        }


        /// <inheritdoc/>
        public override void DisplayValues( Forecast forecast, Units units, ForecastDisplayMode mode ) {
            // Get time values for current mode.
            DateTimeOffset now = DateTimeOffset.UtcNow.ToOffset( forecast.Start.Offset );
            DateTimeOffset date = now.GetDateOffset( );
            DateTimeOffset begin = mode != ForecastDisplayMode.Hour ? date : date.AddHours( now.Hour - ConvertValue.HoursPerDay / 3 );
            int hourOffset = 1 + (int)now.HoursFrom( forecast.Start );

            // Display labels and update layout to accomodate text size.
            forecast.GetWindSpeedValues( units, ref this._windSpeed );
            this.DisplayTraceLabels( forecast, units, date, hourOffset );
            this.InitializeLayout( );

            // Setup scales.
            double traceWidth = this.TraceColumn.ActualWidth;
            double traceHeight = this.PrecipitationRow.ActualHeight;
            var percentScale = new Scale( true, traceHeight, new double[] { 0, 1 } );
            var valueTemplateScale = new Scale( true, traceHeight, new double[] { 0 }, floor: 0 );
            var timeScale = new Scale( false, traceWidth, new double[] { 0, ConvertValue.HoursPerDay } );

            DoubleCollection offsets, heights;
            Pair.TryGetValues( VisitGraphRows(
                Pair.Create( new DoubleCollection( ), new DoubleCollection( ) ),
                ( state, offset, height ) => {
                    state.Item1.Add( offset );
                    state.Item2.Add( height );
                }, includeGapRows: true ),
                out offsets, out heights );

            // Display values.
            int beginOffset = (int)begin.HoursFrom( forecast.Start );
            DisplayTrace( this.PrecipitationTrace, this.CurrentPrecipitationTimeMarker, forecast.PrecipitationPotential, percentScale, timeScale, beginOffset, hourOffset, offsets[0] );
            DisplayTrace( this.CloudCoverTrace, this.CurrentCloudCoverTimeMarker, forecast.SkyCover, percentScale, timeScale, beginOffset, hourOffset, offsets[2] );
            DisplayTrace( this.WindSpeedTrace, this.CurrentWindSpeedTimeMarker, this._windSpeed, valueTemplateScale, timeScale, beginOffset, hourOffset, offsets[4] );

            double fullTraceHeight = heights.Sum( );
            this.DisplayCurrentTime( timeScale, beginOffset, hourOffset, fullTraceHeight );
            this.DisplayNightBackground( forecast, timeScale, date, begin );
            this.DisplayMidnightLine( mode, timeScale, date, begin, heights, fullTraceHeight );
        }


        /// <inheritdoc/>
        protected override UIElementCollection ForecastPanelChildren {
            get { return this.ForecastPanel.Children; }
        }


        #region Private Members

        private DataValues _windSpeed;


        private T VisitGraphRows<T>( T state, Action<T, double, double> visitRow, bool includeGapRows = false ) {
            var traceRows = new[] { this.PrecipitationRow, this.CloudCoverRow, this.WindRow };
            var rows = this.ForecastPanel.RowDefinitions;

            double offset = 0;
            int startIndex = rows.IndexOf( traceRows[0] );
            for( int i = startIndex; i < rows.Count; ++i ) {
                var row = rows[i];
                double height = row.Height.Value;
                int rowIndex = Array.IndexOf( traceRows, row );

                if( rowIndex >= 0 || includeGapRows )
                    visitRow( state, offset, height );

                offset += height;

                if( rowIndex == traceRows.Length - 1 )
                    break;
            }

            return state;
        }


        private void DisplayTraceLabels( Forecast forecast, Units units, DateTimeOffset date, double hourOffset ) {
            const string CurrentValueLabelFormat = "0";

            const string CurrentTimeLabelFormat = "ht";
            DateTimeOffset currentTime = forecast.Start.AddHours( hourOffset );
            this.CurrentTimeLabel.Text = currentTime.ToString( CurrentTimeLabelFormat ).ToLowerInvariant( );

            double currentPrecipitation = forecast.PrecipitationPotential.GetInterpolatedValue( hourOffset, ConvertValue.ToPercentage );
            this.CurrentPrecipitationLabel.Text = currentPrecipitation.ToString( CurrentValueLabelFormat );

            double currentCloudCover = forecast.SkyCover.GetInterpolatedValue( hourOffset, ConvertValue.ToPercentage );
            this.CurrentCloudCoverLabel.Text = currentCloudCover.ToString( CurrentValueLabelFormat );

            string[] windSpeedUnitNames =
                units == Data.Units.Metric
                    ? new[] { "m", "s" }
                    : new[] { "mi", "h" };

            double currentWindSpeed = this._windSpeed.GetInterpolatedValue( hourOffset, ConvertValue.Identity );
            if( currentWindSpeed > 0 )
                currentWindSpeed = Math.Max( 1, currentWindSpeed );

            this.CurrentWindSpeedLabel.Text = currentWindSpeed.ToString( CurrentValueLabelFormat );
            this.CurrentWindSpeedUnitNumerator.Text = windSpeedUnitNames[0];
            this.CurrentWindSpeedUnitDenominator.Text = windSpeedUnitNames[1];
        }

        private void DisplayCurrentTime( Scale timeScale, int beginOffset, int hourOffset, double fullHeight ) {
            int timeOffset = hourOffset - beginOffset;
            double currentPosition = timeScale.ToScreen( timeOffset );
            this.CurrentTimeLabel.Margin = new Thickness { Left = Math.Round( currentPosition - this.CurrentTimeLabel.ActualWidth / 2 ) };


            const int MarkerFrequency = 3;
            const double SmallMarkerSize = 2;
            const double LargeMarkerSize = 8;

            double end = SmallMarkerSize / -2;
            double thickness = this.CurrentTimeMarkers.StrokeThickness;
            int markerOffset = (MarkerFrequency - beginOffset % MarkerFrequency) % MarkerFrequency;
            Scale markerScale = new Scale( timeScale.IsInverted, timeScale.ScreenSize - SmallMarkerSize, new[] { timeScale.Minimum, timeScale.Maximum } );

            var dashes = new DoubleCollection { 0 };
            for( int i = 0; i <= ConvertValue.HoursPerDay; ++i ) {
                double size;
                if( i == timeOffset - markerOffset )
                    size = LargeMarkerSize;
                else if( i % MarkerFrequency == 0 )
                    size = SmallMarkerSize;
                else
                    continue;

                double position = markerScale.ToScreen( i - markerOffset );
                double start = position - size / 2;
                dashes.Add( (start - end) / thickness );
                dashes.Add( size / thickness );

                end = start + size;
            }

            this.CurrentTimeMarkers.StrokeDashArray = dashes;
            this.CurrentTimeMarkers.X2 = timeScale.ScreenSize + LargeMarkerSize;
            this.CurrentTimeMarkers.Y1 = this.CurrentTimeMarkers.Y2 = fullHeight + thickness / 2 + 2;
        }

        private void DisplayNightBackground( Forecast forecast, Scale timeScale, DateTimeOffset date, DateTimeOffset begin ) {
            var location = forecast.Location;
            var yesterday = Ephemerides.CalculateDaylight( location, date.AddDays( -1 ) );
            var today = Ephemerides.CalculateDaylight( location, date );
            var tomorrow = Ephemerides.CalculateDaylight( location, date.AddDays( 1 ) );
            double lastNightStart = timeScale.ToScreen( yesterday.Item2.HoursFrom( begin ) );
            double lastNightEnd = timeScale.ToScreen( today.Item1.HoursFrom( begin ) );
            double nightStart = timeScale.ToScreen( today.Item2.HoursFrom( begin ) );
            double nightEnd = timeScale.ToScreen( tomorrow.Item1.HoursFrom( begin ) );

            var clip = (PathGeometry)this.NightBackground.Clip;
            clip.Figures = VisitGraphRows( new PathFigureCollection( ),
                ( clipFigures, offset, height ) => {
                    clipFigures.Add( CreatePathFigure(
                        new Point( lastNightStart, offset ),
                        new Point( lastNightEnd, offset ),
                        new Point( lastNightEnd, offset + height ),
                        new Point( lastNightStart, offset + height ) ) );
                    clipFigures.Add( CreatePathFigure(
                        new Point( nightStart, offset ),
                        new Point( nightEnd, offset ),
                        new Point( nightEnd, offset + height ),
                        new Point( nightStart, offset + height ) ) );
                } );
        }

        private void DisplayMidnightLine( ForecastDisplayMode mode, Scale timeScale, DateTimeOffset date, DateTimeOffset begin, DoubleCollection heights, double fullHeight ) {
            this.MidnightLine.StrokeDashArray = heights;
            this.MidnightLine.Y2 = fullHeight;

            double midnightHour = date.AddDays( 1 ).HoursFrom( begin ) % ConvertValue.HoursPerDay;
            this.MidnightLine.X1 = this.MidnightLine.X2 = timeScale.ToScreen( midnightHour );

            this.MidnightLine.Opacity =
                mode != ForecastDisplayMode.Day
                    ? 1.0
                    : 0.0;
        }

        #endregion

    }

}
